import { Buffer } from 'buffer';

/**
 * ip类型枚举
 */
export enum IpFamilyEnum {
  IPV4,
  IPV6
}
/**
 * IP 工具类
 */
export class IpTool {

  /* ip v4 正则 */
  public static REGEX_IP_V4: RegExp = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;

  /* ip v6 正则 */
  public static REGEX_IP_V6: RegExp = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i;

  /* 内网IP 正则 */
  public static REGEX_PRIVATE_IP: RegExp = /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.([1][6-9]|[2]\d|3[01])\.\d{1,3}\.\d{1,3}|169\.254\.([0-9]{1,3})\.([0-9]{1,3}))$/;

  /* 掩码IP段 正则 */
  public static REGEX_IP_MASK: RegExp = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\/([1-2]?[0-9]|3[0-2])$/;

  /* IP段 正则 */
  public static REGEX_IP_AREA: RegExp = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])-(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;

  /* 简易IP段 正则 */
  public static REGEX_IP_AREA_SIMPLE: RegExp = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])-(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;

  /**
   * 获取IP段之间的所有IP
   * 
   * @param value IP段
   * @param len 最大程度，超过最大长度报错，返回空
   */
  public static getIps(value: string, len: number = 70000): string[] {
    let ips = [];

    if (IpTool.isMask(value)) {
      let net = IpTool.parseMask(value);
      ips = IpTool.getIpsWithTwoIp(net.first, net.last, len);
    }

    if (IpTool.isArea(value)) {
      let tmp = value.split('-');
      ips = IpTool.getIpsWithTwoIp(tmp[0], tmp[1], len);
    }

    if (IpTool.isAreaSimple(value)) {
      let tmp = value.split('-');
      let last = tmp[0].substring(0, tmp[0].lastIndexOf('.') + 1) + tmp[1];
      ips = IpTool.getIpsWithTwoIp(tmp[0], last, len);
    }

    if (IpTool.isIPV4(value)) {
      ips.push(value);
    }

    return ips;
  }

  /**
   * 获取俩个IP中间的所有IP
   * 
   * @param first 开始IP
   * @param last 结束IP
   * @param len 最大程度，超过最大长度报错，返回空
   */
  public static getIpsWithTwoIp(first: string, last: string, len: number = 70000): string[] {
    let ips = [];
    if (IpTool.isIPV4(first) && IpTool.isIPV4(last)) {
      let start = IpTool.toLong(first);
      let end = IpTool.toLong(last);
      if (start > end) {
        const t = start;
        start = end;
        end = t;
      }
      if (end + 1 - start > len) {
        console.error(`获取的IP数量大于${len}，容易造成浏览器崩溃, 实际使用过程中不建议返回超过太多的IP。【这里直接返回空数组】`)
        return ips;
      }
      for (let i = start; i < end + 1; i++) {
        ips.push(IpTool.fromLong(i));
      }
    }

    return ips;
  }


  /**
   * 是否简易IP段 192.168.1.1-2
   * @param ip 
   */
  public static isAreaSimple(ip: string): boolean {
    return IpTool.REGEX_IP_AREA_SIMPLE.test(ip);
  }

  /**
   * 是否IP段 192.168.1.1 - 192.168.1.1
   * @param ip 
   */
  public static isArea(ip: string): boolean {
    return IpTool.REGEX_IP_AREA.test(ip);
  }

  /**
   * 是否带有掩码IP段 192.168.1.1/32
   * @param ip 
   */
  public static isMask(ip: string): boolean {
    return IpTool.REGEX_IP_MASK.test(ip);
  }

  /**
   * 是否外网IP
   * @param ip 
   */
  public static isPublic(ip: string): boolean {
    return IpTool.isIPV4(ip) && !IpTool.isPrivate(ip);
  }

  /**
   * 是否内网IP
   * @param ip 
   */
  public static isPrivate(ip: string): boolean {
    return IpTool.REGEX_PRIVATE_IP.test(ip);
  }

  /**
   * 是否IP v4
   * @param ip 
   */
  public static isIPV4(ip: string): boolean {
    return IpTool.REGEX_IP_V4.test(ip);
  }

  /**
   * 是否IP v6
   * @param ip 
   */
  public static isIPV6(ip: string): boolean {
    return IpTool.REGEX_IP_V6.test(ip);
  }

  /**
   * 转换成number
   * 
   * @param ip ipv4
   */
  public static toLong(ip: string): number {
    var ipl = 0;
    ip.split('.').forEach(function (octet) {
      ipl <<= 8;
      ipl += parseInt(octet);
    });
    return (ipl >>> 0);
  }

  /**
   * 转换成字符串ip
   * 
   * @param ip ipv4 number
   */
  public static fromLong(ip: number): string {
    return ((ip >>> 24) + '.' +
      (ip >> 16 & 255) + '.' +
      (ip >> 8 & 255) + '.' +
      (ip & 255));
  }

  /**
   * 解析掩码IP段
   * 
   * @param value 掩码IP段
   */
  public static parseMask(value: string): any {
    if (!IpTool.isMask(value)) { return null; }
    let parts = value.split('/');
    let mask = IpTool.fromPrefixLen(parseInt(parts[1], 10));
    return IpTool.subnet(parts[0], mask);
  }

  private static subnet(addr: string, mask: string) {
    let networkAddress = IpTool.toLong(IpTool.mask(addr, mask));
    let maskBuffer = IpTool.toBuffer(mask);
    let maskLength = 0;

    for (let i = 0; i < maskBuffer.length; i++) {
      if (maskBuffer[i] === 0xff) {
        maskLength += 8;
      } else {
        let octet = maskBuffer[i] & 0xff;
        while (octet) {
          octet = (octet << 1) & 0xff;
          maskLength++;
        }
      }
    }

    let numberOfAddresses = Math.pow(2, 32 - maskLength);

    return {
      network: IpTool.fromLong(networkAddress),
      first: numberOfAddresses <= 2 ?
        IpTool.fromLong(networkAddress) :
        IpTool.fromLong(networkAddress + 1),
      last: numberOfAddresses <= 2 ?
        IpTool.fromLong(networkAddress + numberOfAddresses - 1) :
        IpTool.fromLong(networkAddress + numberOfAddresses - 2),
      broadcast: IpTool.fromLong(networkAddress + numberOfAddresses - 1),
      mask: mask,
      maskLength: maskLength,
      numHosts: numberOfAddresses <= 2 ?
        numberOfAddresses : numberOfAddresses - 2,
      length: numberOfAddresses,
      contains: function (other) {
        return networkAddress === IpTool.toLong(IpTool.mask(other, mask));
      }
    };
  }

  private static mask(addr: any, mask: any) {
    addr = IpTool.toBuffer(addr);
    mask = IpTool.toBuffer(mask);
    let result = new Buffer(Math.max(addr.length, mask.length));
    if (addr.length === mask.length) {
      for (let i = 0; i < addr.length; i++) {
        result[i] = addr[i] & mask[i];
      }
    } else if (mask.length === 4) {
      // IPv6 address and IPv4 mask
      // (Mask low bits)
      for (let i = 0; i < mask.length; i++) {
        result[i] = addr[addr.length - 4 + i] & mask[i];
      }
    } else {
      // IPv6 mask and IPv4 addr
      for (let i = 0; i < result.length - 6; i++) {
        result[i] = 0;
      }

      // ::ffff:ipv4
      result[10] = 0xff;
      result[11] = 0xff;
      for (let i = 0; i < addr.length; i++) {
        result[i + 12] = addr[i] & mask[i + 12];
      }
    }

    return IpTool.toString(result);
  }

  private static fromPrefixLen(prefixlen: number): string {
    let len = 4;
    let buff = new Buffer(len);

    for (let i = 0, n = buff.length; i < n; ++i) {
      let bits = 8;
      if (prefixlen < 8) {
        bits = prefixlen;
      }
      prefixlen -= bits;

      buff[i] = ~(0xff >> bits) & 0xff;
    }

    return IpTool.toString(buff);
  }

  private static toString(buff: Buffer, offset?: number, length?: number): string {
    offset = ~~offset;
    length = length || (buff.length - offset);
    let result: any | string = [];
    if (length === 4) {
      // IPv4
      for (let i = 0; i < length; i++) {
        result.push(buff[offset + i]);
      }
      result = result.join('.');
    } else if (length === 16) {
      // IPv6
      for (let i = 0; i < length; i += 2) {
        result.push(buff.readUInt16BE(offset + i).toString(16));
      }
      result = result.join(':');
      result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
      result = result.replace(/:{3,4}/, '::');
    }

    return result;
  }

  private static toBuffer(ip: string, buff?: Buffer, offset?: number) {
    offset = ~~offset;
    let result;
    if (IpTool.isIPV4(ip)) {
      result = buff || new Buffer(offset + 4);
      ip.split(/\./g).map(byte => {
        result[offset++] = parseInt(byte, 10) & 0xff;
      });
    } else if (IpTool.isIPV6(ip)) {
      let sections = ip.split(':', 8);
      let i;
      for (i = 0; i < sections.length; i++) {
        var isv4 = IpTool.isIPV4(sections[i]);
        var v4Buffer;

        if (isv4) {
          v4Buffer = IpTool.toBuffer(sections[i]);
          sections[i] = v4Buffer.slice(0, 2).toString('hex');
        }

        if (v4Buffer && ++i < 8) {
          sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex'));
        }
      }

      if (sections[0] === '') {
        while (sections.length < 8) sections.unshift('0');
      } else if (sections[sections.length - 1] === '') {
        while (sections.length < 8) sections.push('0');
      } else if (sections.length < 8) {
        for (let i = 0; i < sections.length && sections[i] !== ''; i++);
        var argv = [i, 1];
        for (i = 9 - sections.length; i > 0; i--) {
          argv.push('0');
        }
        sections.splice.apply(sections, argv);
      }

      result = buff || new Buffer(offset + 16);
      for (i = 0; i < sections.length; i++) {
        var word = parseInt(sections[i], 16);
        result[offset++] = (word >> 8) & 0xff;
        result[offset++] = word & 0xff;
      }
    }

    if (!result) {
      throw Error('Invalid ip address: ' + ip);
    }

    return result;
  }

}